use std::sync::{Arc, LazyLock};

use openssl::{
    error::ErrorStack,
    ex_data::Index,
    ssl::{Ssl, SslContext, SslContextBuilder, SslMethod, SslMode, SslOptions},
    version,
};

use crate::config::ssl::acceptor::SslAcceptorRuntimeConf;

use super::{
    acceptor::CustomAcceptorBuilder, connector::CustomConnectorBuilder,
};

pub const MAX_HOST_NAME_LENGTH: usize = 255;

pub struct SslInitialization {
    // ssl context index
    pub acceptor_builer_index: Index<SslContext, Arc<CustomAcceptorBuilder>>,
    pub connector_builder_index: Index<SslContext, Arc<CustomConnectorBuilder>>,

    // ssl index
    pub listener_address_index: Index<Ssl, String>,
    pub conn_local_address_index: Index<Ssl, String>,
    pub conn_peer_address_index: Index<Ssl, String>,
    pub acceptor_conf_index: Index<Ssl, Arc<SslAcceptorRuntimeConf>>,
}

impl SslInitialization {
    pub fn get_instance() -> &'static SslInitialization {
        static SSL_INITLIZATION: LazyLock<SslInitialization> =
            LazyLock::new(|| {
                let acceptor_builer_index = SslContext::new_ex_index().unwrap();
                let connector_builder_index =
                    SslContext::new_ex_index().unwrap();
                let listener_address_index = Ssl::new_ex_index().unwrap();
                let conn_local_address_index = Ssl::new_ex_index().unwrap();
                let conn_peer_address_index = Ssl::new_ex_index().unwrap();
                let acceptor_conf_index = Ssl::new_ex_index().unwrap();

                SslInitialization {
                    acceptor_builer_index,
                    connector_builder_index,
                    listener_address_index,
                    conn_local_address_index,
                    conn_peer_address_index,
                    acceptor_conf_index,
                }
            });

        &SSL_INITLIZATION
    }
}

#[allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)]
pub fn ctx_builder(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
    let mut builder = SslContextBuilder::new(method)?;

    let mut opts = SslOptions::ALL
        | SslOptions::NO_COMPRESSION
        | SslOptions::NO_SSLV2
        | SslOptions::SINGLE_DH_USE
        | SslOptions::SINGLE_ECDH_USE;
    opts &= !SslOptions::DONT_INSERT_EMPTY_FRAGMENTS;

    builder.set_options(opts);

    let mut mode = SslMode::AUTO_RETRY
        | SslMode::ACCEPT_MOVING_WRITE_BUFFER
        | SslMode::ENABLE_PARTIAL_WRITE;

    // This is quite a useful optimization for saving memory, but historically
    // caused CVEs in OpenSSL pre-1.0.1h, according to
    // https://bugs.python.org/issue25672
    if version::number() >= 0x1_00_01_08_0 {
        mode |= SslMode::RELEASE_BUFFERS;
    }

    builder.set_mode(mode);

    Ok(builder)
}

pub fn parse_extension_value(ext: &[u8]) -> Result<String, &'static str> {
    let ext_len = ext.len();
    if ext_len < 5 {
        return Err("no enough data");
    }

    let list_len = u16::from_be_bytes([ext[0], ext[1]]);
    if list_len as usize + 2 != ext_len {
        return Err("invalid list length");
    }

    let name_type = ext[2];
    if name_type != 0x00 {
        return Err("invalid name type");
    }

    let name_len = u16::from_be_bytes([ext[3], ext[4]]) as usize;
    if name_len > MAX_HOST_NAME_LENGTH || name_len + 5 > ext_len {
        return Err("invalid name length");
    }

    let name = ext[5..5 + name_len].to_ascii_lowercase();

    String::from_utf8(name).map_err(|_| "invalid host name")
}

#[cfg(test)]
mod tests {
    use super::parse_extension_value;

    #[test]
    fn test_parse_valid() {
        let data: &[u8] = &[
            0x00, 0x0e, // Server Name List Length, 14
            0x00, // Server Name Type - Domain
            0x00, 0x0b, // Server Name Length, 11
            b'e', b'x', b'a', b'm', b'p', b'l', b'e', b'.', b'n', b'e', b't',
        ];
        let sni = parse_extension_value(data).unwrap();
        assert_eq!(&sni, "example.net");
    }

    #[test]
    fn test_parse_invalid() {
        let data: &[u8] = &[
            0x00, 0x0e, // Server Name List Length, 14
            0x00, // Server Name Type - Domain
            0x01, 0x0b, // Server Name Length, 256 + 11
            b'e', b'x', b'a', b'm', b'p', b'l', b'e', b'.', b'n', b'e', b't',
        ];
        assert!(parse_extension_value(data).is_err());
    }
}
